package database

import (
	"database/sql"
	"fmt"
	"strings"
)

// Db object
type Db struct {
	sqlDb *sql.DB
	IsPanic bool
	stmt map[string]*Statement
}

// db connect
func (this *Db) conn(driver string, dsn string, conn int) *Db {
	var e error
	this.sqlDb, e = sql.Open(driver, dsn)
	this.IsPanic = false
	if e != nil {
		panic(e)
	}
	this.sqlDb.SetMaxIdleConns(conn)
	this.Free()
	return this
}

// throw error or return error
func panicError(isPanic bool, sqlStr string, e error) error {
	if isPanic && e != nil {
		panic(fmt.Sprint("[DB] ", e, " # ", sqlStr))
	}
	return e
}

// query rows with sql and params
func (this *Db) Query(sqlStr string, args... interface {}) ([]map[string]string, error) {
	// query rows
	rows, e := this.sqlDb.Query(sqlStr, args...)
	if e != nil {
		return nil, panicError(this.IsPanic, sqlStr, e)
	}
	// close row data resource
	defer rows.Close()
	// get columns
	rs, e2 := rowMapper(rows)
	return rs, panicError(this.IsPanic, sqlStr, e2)
}

// query one row
func (this *Db) One(sqlStr string, args... interface {}) (map[string]string, error) {
	// if no limit keyword in sqlStr, add it
	if !strings.Contains(sqlStr, "LIMIT 1") {
		sqlStr = sqlStr + " LIMIT 1"
	}
	// query and return first one
	// do not panic here, if set panic, it will throw panic when Query method
	rs, e := this.Query(sqlStr, args...)
	if e != nil {
		return nil, e
	}
	if len(rs) < 1 {
		return nil, nil
	}
	return rs[0], nil
}

// exec sqlStr and return insertId(insert) or affectRows(update,delete)
func (this *Db) Exec(sqlStr string, args... interface {}) (int64, error) {
	rs , e := this.sqlDb.Exec(sqlStr, args...)
	if e != nil {
		return -1, panicError(this.IsPanic, sqlStr, e)
	}
	var i int64
	i, e = resultParser(rs)
	return i, panicError(this.IsPanic, sqlStr, e)
}

// prepare sqlStr and return *Statement.
// *Statement objects will be cached.
// It returns cache one if prepared, otherwise return error
func (this *Db) Prepare(sqlStr string) (*Statement, error) {
	if this.stmt[sqlStr] == nil {
		s := &Statement{}
		s.QueryString = sqlStr
		var e error
		s.sqlStmt, e = this.sqlDb.Prepare(sqlStr)
		if e != nil {
			return nil, panicError(this.IsPanic, sqlStr, e)
		}
		s.isPanic = this.IsPanic
		this.stmt[sqlStr] = s
	}
	return this.stmt[sqlStr], nil
}

// Begin transaction from db
// Return *Transaction struct, with panic setting
func (this *Db) Begin() (*Transaction, error) {
	tx, e := this.sqlDb.Begin()
	if e != nil {
		return nil, panicError(this.IsPanic, "begin transaction", e)
	}
	t := &Transaction{}
	t.IsPanic = this.IsPanic
	t.tx = tx
	return t, nil
}

// get *sql.DB struct in db object
func (this *Db) SqlDb() *sql.DB {
	return this.sqlDb
}

// create New Table object from a db connection
func (this *Db) Table(tableName string) *Table {
	return NewTable(tableName, this)
}

// create new sql from a db connection
func (this *Db) Sql(table string, column... string) *Sql {
	return NewSql(table, column...)
}

// get *Record struct with this db connection
func (this *Db) Record(data interface {}) *Record {
	r := &Record{}
	r.parseData(data)
	r.db = this
	return r
}

// free cached statements in map
func (this *Db) Free(sqlStr...string) {
	if len(sqlStr) < 1 {
		this.stmt = map[string]*Statement{}
		return
	}
	for _, k := range sqlStr {
		delete(this.stmt, k)
	}
}

// close db connection
func (this *Db) Close() {
	if this.sqlDb != nil {
		this.sqlDb.Close()
	}
}

// create new db struct
func NewDb(driver string, dsn string, conn int) *Db {
	return new(Db).conn(driver, dsn, conn)
}
